package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.product.model.SkuImage;
import com.atguigu.gmall.product.model.SkuInfo;
import com.atguigu.gmall.product.service.ProductApiService;
import com.atguigu.gmall.product.service.SkuImageService;
import com.atguigu.gmall.product.service.SkuInfoService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.xml.crypto.Data;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class ProductApiServiceImpl implements ProductApiService {
    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImageService skuImageService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 根据skuId获取SkuInfo  包含Image
     *
     * @param skuId
     * @return
     */

    public SkuInfo getSkuInfoAndImageFromDB(Long skuId) {
        //根据ID查询skuinfo的实体类
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        if (skuInfo != null) {
            //根据skuid进行查找是否存在图片列表
            LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SkuImage::getSkuId, skuId);
            List<SkuImage> imageList = skuImageService.list(queryWrapper);
            //判断是否有图片
            if (!CollectionUtils.isEmpty(imageList)) {
                skuInfo.setSkuImageList(imageList);
                return skuInfo;
            }
        }
        return null;
    }

    /**
     * 根据skuId获取SkuInfo  包含Image
     *
     * @param skuId
     * @return
     */

    public SkuInfo getSkuInfoAndImageredisTemplate(Long skuId) {
        try {
            //1.优先从分布缓存Redis中获取商品信息
            //1.1 构建缓存业务数据Key 形式 sku:1:info
            String dateKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            //1.2从缓存中获取数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dateKey);

            //如果命中直接返回结果
            if (skuInfo != null) {
                return skuInfo;
            }
            //2.尝试获取分布锁
            //2.1构建商品的分布式锁的Key  sku:1:lock
            String lockSku = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
            //2.2 构建分布式锁值  采用uuid 避免锁的相互释放
            String lockValue = UUID.randomUUID().toString().replaceAll("-", "");
            //2.3 通过 set ex nx 命令进行实现分布锁 todo 时间定值
            Boolean flog = redisTemplate.opsForValue().setIfAbsent(lockSku, lockValue, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
            //2.4如果获取锁成功-进行第三部
            if (flog) {
                try {
                    //3 执行业务 todo 记得释放锁
                    //3.1查询数据库业务
                    SkuInfo skuInfo1 = this.getSkuInfoAndImageFromDB(skuId);
                    if (skuInfo1 == null) {
                        //数据库不存在  将数据放到缓存中 缓存时间10分钟
                        redisTemplate.opsForValue().set(dateKey, skuInfo1, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo1;
                    } else {
                        //3.4 查询数据存在时  将数据放到缓存中  缓存时间24小时
                        redisTemplate.opsForValue().set(dateKey, skuInfo1, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo1;
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //将锁释放
                    //3.5将锁释放 采用lua脚本 保证判断删除锁的原子性
                    //3.5.1 创建脚本对象  封装lua脚本语言 KEYS{1} 传入锁的名称  ARGV[1] :传入当前线程锁的
                    //4.2为脚本的动态参数站位符设置值
                    //4.3执行lua脚本

                    String luaText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
                    script.setScriptText(luaText);
                    script.setResultType(Long.class);
                    //                redisTemplate.delete(lockSku);
                    redisTemplate.execute(script, Arrays.asList(lockSku), new String[]{lockValue});
                }
            } else {
                TimeUnit.MILLISECONDS.sleep(500);
                return this.getSkuInfoAndImage(skuId);
            }
        } catch (Exception e) {
            //如果有不可预知的错误  Redis机芯 兜底处理
            return this.getSkuInfoAndImageFromDB(skuId);
        }

    }

    @Override
    public SkuInfo getSkuInfoAndImage(Long skuId) {
        try {
            //1.先从缓存中获取数据
            String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            if (skuInfo != null) {
                return skuInfo;
            }

            String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
            RLock lock = redissonClient.getLock(lockKey);
            boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX2, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
            if (flag) {
                try {
                    //抢到了锁
                    //执行业务
                    //3.1 查询数据库操作
                    SkuInfo skuInfo1 = this.getSkuInfoAndImageFromDB(skuId);
                    if (skuInfo1 != null) {
                        redisTemplate.opsForValue().set(dataKey, skuInfo1, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo1;
                    } else {
                        redisTemplate.opsForValue().set(dataKey, skuInfo1, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo1;
                    }
                } finally {
                    lock.unlock();
                }
            }//没有抢到锁
            else {
                TimeUnit.MILLISECONDS.sleep(500);
                this.getSkuInfoAndImage(skuId);
            }
        } catch (InterruptedException e) {
            //兜底
            return this.getSkuInfoAndImageFromDB(skuId);
        }


        return null;
    }
}
